﻿using Bling.DSL;
using Bling.Util;
using System.Collections.Generic;
using System;
using System.Linq;
using Bling.NewOps;
using Bling.NewVecs;

namespace Bling.DSL {
  using EvalCore;

  public abstract partial class Expr : IExpr {
    public abstract Expr BaseTranslate(ITranslator Translator);
  }

  public abstract partial class Expr<T> : Expr, IExpr<T> {
    protected virtual NewEval<EVAL>.Info<T> Eval<EVAL>(NewEval<EVAL> txt) where EVAL : NewEval<EVAL> {
      throw new NotImplementedException();
    }
    internal NewEval<EVAL>.Info<T> Eval000<EVAL>(NewEval<EVAL> txt) where EVAL : NewEval<EVAL> { return Eval(txt); }

    public virtual Expr<T> Translate(ITranslator Translator) {
      throw new NotImplementedException();
    }
    public sealed override Expr BaseTranslate(ITranslator Translator) {
      return Translate(Translator);
    }
    public string NewDump {
      get {
        var share = new ComputeTemps();
        share.Process(this);
        var replace = new ComputeTemps.ReplaceTemps(share);
        var e = replace.Translate(this);
        var io = new System.IO.StringWriter();
        int c = 0;
        foreach (var s in replace.End()) {
          s.Name = "S" + c;
          io.WriteLine(s.Name + " = " + s.BaseUnderlying + ";");
          c++;
        }
        io.WriteLine(e);
        return io.ToString();
      }
    }
  }
  public partial interface INewEval<EVAL> where EVAL : NewEval<EVAL> {
    NewEval<EVAL>.Info<T> DoNewEval<T>(Expr<T> Expr);
  }
  public abstract partial class NewEval<EVAL> : Eval, INewEval<EVAL> where EVAL : NewEval<EVAL> {
    public new interface Info : Eval.Info { }
    public new interface Info<T> : Eval.Info<T>, Info { }
    public virtual Info<T> DoNewEval<T>(Expr<T> Expr) { return Expr.Eval000(this); }
  }
  public abstract partial class ProxyExpr<T> : Expr<T>, IProxyExpr {
    public override Expr<T> Translate(ITranslator Translator) {
      return Make0<T>(Translator.Translate);
    }
    protected override NewEval<EVAL>.Info<T> Eval<EVAL>(NewEval<EVAL> txt) {
      return txt.DoNewEval(Underlying);
    }
  }
}
namespace Bling.EvalCore {
  using Bling.NewOps.Conversions;
  public interface ITranslator {
    Expr<T> Translate<T>(Expr<T> E);
  }
  public interface ITemporaryExpr : IProxyExpr {
    string Name { get; set; }
    ITemporaryExpr Recast0(ComputeTemps.ReplaceTemps Replace);
  }
  public class TemporaryExpr<T> : ProxyExpr<T>, ITemporaryExpr {
    public string Name { get; set; }
    public TemporaryExpr(Expr<T> Underlying) : base(Underlying) { }
    protected override string ToString1() {
      if (Name == null) return "S" + GetHashCode();
      else return Name;
    }
    protected override ProxyExpr<S> Make<S>(Expr<S> u) {
      false.Assert();
      return new TemporaryExpr<S>(u);
    }
    public override Expr<T> Translate(ITranslator Translator) {
      if (Translator is ComputeTemps) return Underlying.Translate(Translator);
      return base.Translate(Translator);
    }

    public ITemporaryExpr Recast0(ComputeTemps.ReplaceTemps Replace) { return Recast(Replace); }
    public TemporaryExpr<T> Recast(ComputeTemps.ReplaceTemps Replace) {
      if (Replace.SortedB.ContainsKey(this)) return (TemporaryExpr<T>)Replace.SortedB[this];
      var Underlying = this.Underlying.Translate(Replace);
      var ret = new TemporaryExpr<T>(Underlying) { Name = Name };
      Replace.SortedA.Add(ret);
      Replace.SortedB[this] = ret;
      return ret;
    }
  }
  public class ComputeTemps : ITranslator, ExprVisitor<int> {
    private readonly Dictionary<Expr,int> Visited = new Dictionary<Expr,int>();
    public readonly Dictionary<Expr, ITemporaryExpr> Shared = new Dictionary<Expr, ITemporaryExpr>();
    private readonly Queue<Expr> ToProcess = new Queue<Expr>();

    public Expr<T> Translate<T>(Expr<T> E) {
      if (!(E is Constant<T>)) // no point in sharing constants.
        ToProcess.Enqueue(E);
      return E;
    }
    public void Process<T>(Expr<T> E) { // process a top level expression
      Translate(E);
      while (ToProcess.Count != 0) {
        var EE = ToProcess.Dequeue();
        if (!Visited.ContainsKey(EE)) {
          Visited.Add(EE, Visited.Count);
          // recurse.
          EE.BaseTranslate(this);
        } else {
          EE.Visit(this);
        }
      }
    }
    public int Visit<T>(Expr<T> value) {
      Shared.Add(value, new TemporaryExpr<T>(value) { });
      return 0;
    }
    public class ReplaceTemps : ITranslator {
      private readonly ComputeTemps Outer;
      public readonly List<ITemporaryExpr> SortedA = new List<ITemporaryExpr>();
      internal readonly Dictionary<ITemporaryExpr,ITemporaryExpr> SortedB = new Dictionary<ITemporaryExpr,ITemporaryExpr>();
      public ReplaceTemps(ComputeTemps Outer) {
        this.Outer = Outer;
        foreach (var e in Outer.Shared) e.Value.Recast0(this);
      }
      public Expr<T> Go<T>(Expr<T> E) { return E.Translate(this); }
      public List<ITemporaryExpr> End() { return SortedA; } 
      public Expr<T> Translate<T>(Expr<T> E) {
        if (Outer.Shared.ContainsKey(E)) {
          return ((TemporaryExpr<T>)Outer.Shared[E]).Recast(this);
        } else return E.Translate(this);
      }
    }
  }

  public class FindBlocks : ITranslator {
    public Expr<T> Translate<T>(Expr<T> E) {
      if (E is Condition<T>) {


      }
      return E.Translate(this);
    }
  }


  // 1. what temps do we need?
  // 2. when do we need them?
  // 3. optimize....
  // 4. push into an existing scope or create a new one?

  // var t1 = if (t0) 5 else 4
  // var t2 = t1 + 3
  // var t3 = if (t0) t2 else t5



  // PRE: they should be shared.
  public abstract class BlockEval<EVAL> : NewEval<EVAL> where EVAL : NewEval<EVAL> {
    private readonly ComputeTemps Share = new ComputeTemps();
    private readonly ComputeTemps.ReplaceTemps RP;
    public BlockEval() {
      RP = new ComputeTemps.ReplaceTemps(Share);
    }
    // prime share with all the expressions. 
    public void Process<T>(Expr<T> E) { Share.Process(E); }
    public void EndProcess() {
      int c = 0; // set name on all shares.
      foreach (var s in Share.Shared) {
        s.Value.Name = "t" + c;
        c += 1;
      }
    }
    protected abstract class Scope {
      public Scope Parent { get; internal set; }
      // needed for all computations that take place in the scope. 
      public List<ITemporaryExpr> Temps = new List<ITemporaryExpr>();

      public void Clean() {
        List<ITemporaryExpr> Delete = null;
        foreach (var t in Temps) {
          var p = Parent;
          while (p != null) {
            if (p.Temps.Contains(t)) {
              if (Delete == null) Delete = new List<ITemporaryExpr>();
              Delete.Add(t); break;
            } else p = p.Parent;
          }
        }
        if (Delete != null) 
          foreach (var t in Delete) 
            Temps.Remove(t);
      }
    }
  }
  public interface IConditionEval<EVAL> : INewEval<EVAL> where EVAL : NewEval<EVAL> {
    NewEval<EVAL>.Info<T> Eval<T>(Expr<bool> Test, Expr<T> IfTrue, Expr<T> IfFalse);
  }
  public interface ICondition : IExpr {
    Expr<bool> BaseTest { get; }
    Expr BaseIfTrue { get; }
    Expr BaseIfFalse { get; }
  }
  public abstract class BlockSpec {}
  public class ConditionSpec : BlockSpec {
    public readonly Expr<bool> Test;
    public ConditionSpec(Expr<bool> Test) { this.Test = Test; }
    public override int GetHashCode() {
      return Test.GetHashCode();
    }
    public override bool Equals(object obj) {
      return obj is ConditionSpec && ((ConditionSpec)obj).Test.Equals(Test);
    }
  }
  public class LoopSpec<S> : BlockSpec {
    public readonly Expr<S> InitS;
    public readonly Expr<S> StepS;
    public readonly Expr<bool> Condition;
    public LoopSpec(Expr<S> InitS, Expr<S> StepS, Expr<bool> Condition) { this.InitS = InitS; this.StepS = StepS; this.Condition = Condition; }
    public override int GetHashCode() {
      return InitS.GetHashCode() + StepS.GetHashCode() + Condition.GetHashCode();
    }
    public override bool Equals(object obj) {
      return obj is LoopSpec<S> &&
        ((LoopSpec<S>)obj).InitS.Equals(InitS) &&
        ((LoopSpec<S>)obj).StepS.Equals(StepS) &&
        ((LoopSpec<S>)obj).Condition.Equals(Condition);
    }
  }
  public class TableSpec : BlockSpec {
    public readonly Expr<int> Index;
    public readonly int At;
    public TableSpec(Expr<int> Index, int At) { this.Index = Index; this.At = At; }
    public override int GetHashCode() {
      return Index.GetHashCode() + At.GetHashCode();
    }
    public override bool Equals(object obj) {
      return obj is TableSpec &&
        ((TableSpec)obj).Index.Equals(Index) &&
        ((TableSpec)obj).At.Equals(At);
    }
  }
  
  public class Condition<T> : Expr<T>, ICondition {
    public readonly Expr<bool> Test;
    public readonly Expr<T> IfTrue;
    public readonly Expr<T> IfFalse;
    public bool? IsDynamic = null;
    public Condition(Expr<bool> Test, Expr<T> IfTrue, Expr<T> IfFalse) {
      this.Test = Test; this.IfTrue = IfTrue; this.IfFalse = IfFalse;
    }
    protected override string ToString1() {
      return Test + " ? " + IfTrue + " : " + IfFalse;
    }
    protected override int GetHashCode0() {
      return Test.GetHashCode() + IfTrue.GetHashCode() + IfFalse.GetHashCode();
    }
    protected override bool Equals0(Expr<T> e) {
      if (!(e is Condition<T>)) return false;
      var c = (Condition<T>)e;
      return c.Test.Equals(Test) && c.IfTrue.Equals(IfTrue) && c.IfFalse.Equals(IfFalse);
    }
    protected override NewEval<EVAL>.Info<T> Eval<EVAL>(NewEval<EVAL> txt) {
      if (txt is IConditionEval<EVAL>) return ((IConditionEval<EVAL>)txt).Eval(Test, IfTrue, IfFalse);
      return base.Eval<EVAL>(txt);
    }
    public override Expr<T> Translate(ITranslator Translator) {
      return new Condition<T>(Translator.Translate(Test), Translator.Translate(IfTrue), Translator.Translate(IfFalse));
    }
    protected override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      throw new NotImplementedException();
    }
    public Expr<bool> BaseTest { get { return Test; } }
    public Expr BaseIfTrue { get { return IfTrue; } }
    public Expr BaseIfFalse { get { return IfFalse; } }
  }
  public class Loop<S,T> : Expr<T> {
    public readonly Expr<S> InitS;
    public readonly Expr<T> InitT;
    public readonly Func<Expr<S>, Expr<T>, Expr<bool>> Continue;
    public readonly Func<Expr<S>, Expr<T>, Expr<S>> StepS;
    public readonly Func<Expr<S>, Expr<T>, Expr<T>> StepT;

    public Loop(Expr<S> InitS, Expr<T> InitT, Func<Expr<S>, Expr<T>, Expr<bool>> Continue,
                Func<Expr<S>, Expr<T>, Expr<S>> StepS, Func<Expr<S>, Expr<T>, Expr<T>> StepT) {
      this.InitS = InitS;
      this.InitT = InitT;
      this.Continue = Continue;
      this.StepS = StepS;
      this.StepT = StepT;
    }
    public Loop(Expr<S> InitS, Expr<T> InitT, Func<Expr<S>, Expr<bool>> Continue,
                Func<Expr<S>, Expr<T>, Expr<S>> StepS, Func<Expr<T>, Expr<T>> StepT) :
      this(InitS, InitT, (s, t) => Continue(s), StepS, (s,t) => StepT(t)) {   }

    protected override string ToString1() {
      var idx = this.IdentityHashCode % 100;
      var s = new ParameterExpr<S>() { Name = "s" + idx };
      var t = new ParameterExpr<T>() { Name = "t" + idx };
      return "[InitS=" + InitS + ", InitT=" + InitT + ", Continue=" + Continue(s, t) +
        ", StepS=" + StepS(s,t) + ", StepT=" + StepT(s,t) + "]";
    }
    protected override int GetHashCode0() {
      var idx = this.IdentityHashCode % 100;
      var s = new ParameterExpr<S>() { Name = "s" + idx };
      var t = new ParameterExpr<T>() { Name = "t" + idx };
      return InitS.GetHashCode() + InitT.GetHashCode() + Continue(s, t).GetHashCode() +
        StepS(s, t).GetHashCode() + StepT(s, t).GetHashCode();
    }
    protected override bool Equals0(Expr<T> e) {
      var idx = this.IdentityHashCode % 100;
      var s = new ParameterExpr<S>() { Name = "s" + idx };
      var t = new ParameterExpr<T>() { Name = "t" + idx };
      if (!(e is Loop<S, T>)) return false;
      var loop = (Loop<S, T>)e;
      if (!InitS.Equals(loop.InitS)) return false;
      if (!InitT.Equals(loop.InitT)) return false;
      if (!Continue(s, t).Equals(loop.Continue(s, t))) return false;
      if (!StepS(s, t).Equals(loop.StepS(s, t))) return false;
      if (!StepT(s, t).Equals(loop.StepT(s, t))) return false;
      return true;
    }


    protected override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      throw new NotImplementedException();
    }
  }
  public class TableLookup<T> : Expr<T> {
    public readonly Func<int,Expr<T>> Table;
    public readonly int Count;
    public readonly Expr<int> Index;
    public TableLookup(Func<int, Expr<T>> Table, int Count, Expr<int> Index) {
      this.Table = Table; this.Count = Count; this.Index = Index;
    }
    public TableLookup(Expr<T>[] Table, Expr<int> Index) : this(i => Table[i], Table.Length, Index) {}

    protected override string ToString1() {
      var str = "{ ";
      for (int i = 0; i < Count; i++) {
        if (i > 0) str += ", ";
        str += Table(i).ToString();
      }
      return str + " }[" + Index + "]";
    }
    protected override int GetHashCode0() {
      var hc = Index.GetHashCode() + Count.GetHashCode();
      for (int i = 0; i < Count; i++) hc += Table(i).GetHashCode();
      return hc;
    }
    protected override bool Equals0(Expr<T> e) {
      if (!(e is TableLookup<T>)) return false;
      var te = (TableLookup<T>)e;
      if (Count != te.Count) return false;
      if (!Index.Equals(te.Index)) return false;
      for (int i = 0; i < Count; i++)
        if (!Table(i).Equals(te.Table(i))) return false;
      return true;
    }
    public override Expr<T> Translate(ITranslator Translator) {
      return new TableLookup<T>(i => Translator.Translate(Table(i)), Count, Translator.Translate(Index));
    }
    protected override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      throw new NotImplementedException();
    }
  }


}